@cj-tech-master/excelts 9.6.1 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +18 -3
  2. package/README_zh.md +18 -3
  3. package/dist/browser/modules/excel/cell.d.ts +4 -0
  4. package/dist/browser/modules/excel/note.js +5 -1
  5. package/dist/browser/modules/excel/row.js +35 -2
  6. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  7. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
  8. package/dist/browser/modules/excel/types.d.ts +81 -0
  9. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
  10. package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
  11. package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
  12. package/dist/browser/modules/excel/workbook.browser.js +32 -2
  13. package/dist/browser/modules/excel/worksheet.d.ts +31 -1
  14. package/dist/browser/modules/excel/worksheet.js +83 -0
  15. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  16. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  17. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  18. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  19. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  20. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  21. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  22. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  23. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  24. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  25. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  26. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  27. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  28. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  29. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  30. package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
  31. package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
  32. package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
  33. package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
  34. package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
  35. package/dist/browser/modules/pdf/font/font-manager.js +35 -18
  36. package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
  37. package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
  38. package/dist/browser/modules/word/advanced/field-engine.js +45 -20
  39. package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
  40. package/dist/browser/modules/word/advanced/glossary.js +8 -9
  41. package/dist/browser/modules/word/advanced/math-convert.js +94 -12
  42. package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
  43. package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
  44. package/dist/browser/modules/word/advanced/style-map.js +31 -10
  45. package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
  46. package/dist/browser/modules/word/builder/run-builders.js +7 -1
  47. package/dist/browser/modules/word/constants.d.ts +4 -0
  48. package/dist/browser/modules/word/constants.js +5 -1
  49. package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
  50. package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
  51. package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
  52. package/dist/browser/modules/word/convert/html/html-import.js +167 -14
  53. package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
  54. package/dist/browser/modules/word/convert/html/html.js +1 -1
  55. package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  56. package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
  57. package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
  58. package/dist/browser/modules/word/convert/odt/odt.js +407 -56
  59. package/dist/browser/modules/word/html.d.ts +2 -2
  60. package/dist/browser/modules/word/html.js +1 -1
  61. package/dist/browser/modules/word/index.base.d.ts +3 -3
  62. package/dist/browser/modules/word/index.base.js +1 -1
  63. package/dist/browser/modules/word/layout/layout-full.js +326 -19
  64. package/dist/browser/modules/word/layout/render-page.js +35 -8
  65. package/dist/browser/modules/word/markdown.d.ts +1 -1
  66. package/dist/browser/modules/word/query/compat.d.ts +10 -2
  67. package/dist/browser/modules/word/query/compat.js +29 -21
  68. package/dist/browser/modules/word/reader/docx-reader.js +105 -2
  69. package/dist/browser/modules/word/reader/math-parser.js +8 -2
  70. package/dist/browser/modules/word/security/cfb-reader.js +5 -5
  71. package/dist/browser/modules/word/types.d.ts +96 -1
  72. package/dist/browser/modules/word/writer/docx-packager.js +108 -2
  73. package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
  74. package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
  75. package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
  76. package/dist/browser/modules/word/writer/math-writer.js +7 -2
  77. package/dist/browser/utils/font-metrics.d.ts +8 -0
  78. package/dist/browser/utils/font-metrics.js +43 -0
  79. package/dist/browser/utils/theme-colors.js +4 -1
  80. package/dist/cjs/modules/excel/note.js +5 -1
  81. package/dist/cjs/modules/excel/row.js +35 -2
  82. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
  83. package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
  84. package/dist/cjs/modules/excel/workbook.browser.js +31 -1
  85. package/dist/cjs/modules/excel/worksheet.js +83 -0
  86. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  87. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  88. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  89. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  90. package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  91. package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  92. package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  93. package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
  94. package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  95. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  96. package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
  97. package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
  98. package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
  99. package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
  100. package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
  101. package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
  102. package/dist/cjs/modules/word/advanced/glossary.js +8 -9
  103. package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
  104. package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
  105. package/dist/cjs/modules/word/advanced/style-map.js +31 -10
  106. package/dist/cjs/modules/word/builder/run-builders.js +7 -1
  107. package/dist/cjs/modules/word/constants.js +5 -1
  108. package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
  109. package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
  110. package/dist/cjs/modules/word/convert/html/html.js +2 -1
  111. package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
  112. package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
  113. package/dist/cjs/modules/word/html.js +2 -1
  114. package/dist/cjs/modules/word/index.base.js +4 -3
  115. package/dist/cjs/modules/word/layout/layout-full.js +325 -18
  116. package/dist/cjs/modules/word/layout/render-page.js +35 -8
  117. package/dist/cjs/modules/word/query/compat.js +29 -21
  118. package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
  119. package/dist/cjs/modules/word/reader/math-parser.js +8 -2
  120. package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
  121. package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
  122. package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
  123. package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
  124. package/dist/cjs/modules/word/writer/math-writer.js +7 -2
  125. package/dist/cjs/utils/font-metrics.js +44 -0
  126. package/dist/cjs/utils/theme-colors.js +4 -1
  127. package/dist/esm/modules/excel/note.js +5 -1
  128. package/dist/esm/modules/excel/row.js +35 -2
  129. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
  130. package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
  131. package/dist/esm/modules/excel/workbook.browser.js +32 -2
  132. package/dist/esm/modules/excel/worksheet.js +83 -0
  133. package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  134. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  135. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  136. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  137. package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  138. package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  139. package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  140. package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  141. package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  142. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  143. package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
  144. package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
  145. package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
  146. package/dist/esm/modules/pdf/font/font-manager.js +35 -18
  147. package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
  148. package/dist/esm/modules/word/advanced/field-engine.js +45 -20
  149. package/dist/esm/modules/word/advanced/glossary.js +8 -9
  150. package/dist/esm/modules/word/advanced/math-convert.js +94 -12
  151. package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
  152. package/dist/esm/modules/word/advanced/style-map.js +31 -10
  153. package/dist/esm/modules/word/builder/run-builders.js +7 -1
  154. package/dist/esm/modules/word/constants.js +5 -1
  155. package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
  156. package/dist/esm/modules/word/convert/html/html-import.js +167 -14
  157. package/dist/esm/modules/word/convert/html/html.js +1 -1
  158. package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
  159. package/dist/esm/modules/word/convert/odt/odt.js +407 -56
  160. package/dist/esm/modules/word/html.js +1 -1
  161. package/dist/esm/modules/word/index.base.js +1 -1
  162. package/dist/esm/modules/word/layout/layout-full.js +326 -19
  163. package/dist/esm/modules/word/layout/render-page.js +35 -8
  164. package/dist/esm/modules/word/query/compat.js +29 -21
  165. package/dist/esm/modules/word/reader/docx-reader.js +105 -2
  166. package/dist/esm/modules/word/reader/math-parser.js +8 -2
  167. package/dist/esm/modules/word/security/cfb-reader.js +5 -5
  168. package/dist/esm/modules/word/writer/docx-packager.js +108 -2
  169. package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
  170. package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
  171. package/dist/esm/modules/word/writer/math-writer.js +7 -2
  172. package/dist/esm/utils/font-metrics.js +43 -0
  173. package/dist/esm/utils/theme-colors.js +4 -1
  174. package/dist/iife/excelts.iife.js +496 -59
  175. package/dist/iife/excelts.iife.js.map +1 -1
  176. package/dist/iife/excelts.iife.min.js +39 -39
  177. package/dist/types/modules/excel/cell.d.ts +4 -0
  178. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  179. package/dist/types/modules/excel/types.d.ts +81 -0
  180. package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
  181. package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
  182. package/dist/types/modules/excel/worksheet.d.ts +31 -1
  183. package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  184. package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  185. package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  186. package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  187. package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  188. package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
  189. package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
  190. package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
  191. package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
  192. package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
  193. package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
  194. package/dist/types/modules/word/constants.d.ts +4 -0
  195. package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
  196. package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
  197. package/dist/types/modules/word/convert/html/html.d.ts +2 -2
  198. package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  199. package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
  200. package/dist/types/modules/word/html.d.ts +2 -2
  201. package/dist/types/modules/word/index.base.d.ts +3 -3
  202. package/dist/types/modules/word/markdown.d.ts +1 -1
  203. package/dist/types/modules/word/query/compat.d.ts +10 -2
  204. package/dist/types/modules/word/types.d.ts +96 -1
  205. package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
  206. package/dist/types/utils/font-metrics.d.ts +8 -0
  207. package/package.json +3 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Build Status](https://github.com/cjnoname/excelts/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/cjnoname/excelts/actions/workflows/ci.yml)   [中文](README_zh.md)
4
4
 
5
- The TypeScript ecosystem is heavily fragmented when it comes to document and data processing. To handle Excel, PDF, CSV, XML, ZIP, and streaming, developers often need to pull in a different package for each task — and then yet another set of packages to make them work in the browser, plus separate streaming wrappers on top. These libraries vary in API style, quality, and maintenance status, creating a tax on every project that needs more than one of them.
5
+ The TypeScript ecosystem is heavily fragmented when it comes to document and data processing. To work with spreadsheets, documents, PDFs, and the many data and archive formats around them, developers often need to pull in a different package for each task — and then yet another set of packages to make them work in the browser, plus separate streaming wrappers on top. These libraries vary in API style, quality, and maintenance status, creating a tax on every project that needs more than one of them.
6
6
 
7
7
  ExcelTS was built to fix this. One package, one consistent API, one codebase — working identically across Node.js, Bun, and browsers. Streaming is a first-class citizen in every module, not an afterthought bolted on through a third-party adapter. The goal is simple: install once, import what you need, and get the same reliable behavior everywhere — with maximum streaming performance out of the box.
8
8
 
@@ -12,13 +12,13 @@ ExcelTS is a zero-dependency TypeScript toolkit for spreadsheets and documents:
12
12
 
13
13
  - **AI-Friendly** — Clean, consistent API designed for AI coding agents. Every module has comprehensive documentation and runnable examples for AI to learn from
14
14
  - **Zero Runtime Dependencies** — Pure TypeScript, no external packages
15
- - **Eight Modules** — Excel, Formula, PDF, CSV, Markdown, XML, Archive, Stream
15
+ - **Nine Modules** — Excel, Word, Formula, PDF, CSV, Markdown, XML, Archive, Stream
16
16
  - **Cross-Platform** — Node.js 22+, Bun, Chrome 89+, Firefox 102+, Safari 14.1+
17
17
  - **ESM First** — Native ES Modules with CommonJS compatibility and full tree-shaking
18
18
 
19
19
  ## Modules
20
20
 
21
- ExcelTS is organized into eight standalone modules. Each module has its own documentation and runnable examples.
21
+ ExcelTS is organized into nine standalone modules. Each module has its own documentation and runnable examples.
22
22
 
23
23
  ### Excel — XLSX/JSON Workbook Manager
24
24
 
@@ -27,6 +27,13 @@ Create, read, and modify Excel spreadsheets with full styling, formulas, images,
27
27
  - [Documentation](src/modules/excel/README.md) | [中文](src/modules/excel/README_zh.md)
28
28
  - [Examples](src/modules/excel/examples/)
29
29
 
30
+ ### Word — DOCX Document Processor
31
+
32
+ Read, write, and manipulate DOCX files with a full builder, reader, and converter surface. Build documents with headings, tables, images, lists, headers/footers, drawing shapes, math, and charts. Read and modify existing files with text search/replace, format-aware queries, and bookmark/comment lookup. Convert to and from HTML and Markdown, bridge Excel workbooks into Word tables, and render Word straight to PDF. Advanced features include a template engine, form fields, OpenDoPE data binding, font embedding with subsetting, track-changes accept/reject, document diff/merge, streaming writer, password protection, Agile-encryption decryption, and digital-signature inspection.
33
+
34
+ - [Documentation](src/modules/word/README.md) | [中文](src/modules/word/README_zh.md)
35
+ - [Examples](src/modules/word/examples/)
36
+
30
37
  ### Formula — Excel-Compatible Calculation Engine
31
38
 
32
39
  Standalone 433-function calculation engine with tokenizer, parser, dependency graph, dynamic-array spill, and `LAMBDA`/`LET`/`MAP`/`REDUCE` support. Ships as a separate subpath so it stays out of bundles that only need to read/write XLSX. **Works in two modes**: paired with `Workbook` via `installFormulaEngine()`, or standalone on any `WorkbookLike` host via `calculateFormulas()` — the engine itself has zero excel runtime dependencies.
@@ -150,6 +157,14 @@ const archive = await zip().add("hello.txt", "Hello!").bytes();
150
157
  import { parseMarkdown, formatMarkdown } from "@cj-tech-master/excelts/markdown";
151
158
  const table = parseMarkdown("| A | B |\n|---|---|\n| 1 | 2 |");
152
159
 
160
+ // Word — create, read, and convert DOCX
161
+ import { Document, toBuffer, readDocx } from "@cj-tech-master/excelts/word";
162
+ const wdoc = Document.create();
163
+ Document.addHeading(wdoc, "Report", 1);
164
+ Document.addParagraph(wdoc, "Generated by ExcelTS.");
165
+ const docxBytes = await toBuffer(Document.build(wdoc));
166
+ const parsedDocx = await readDocx(docxBytes); // round-trip read
167
+
153
168
  // Formula — opt-in calculation engine (kept out of the base bundle)
154
169
  //
155
170
  // Mode A: paired with Workbook — enables wb.calculateFormulas()
package/README_zh.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![构建状态](https://github.com/cjnoname/excelts/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/cjnoname/excelts/actions/workflows/ci.yml)   [English](README.md)
4
4
 
5
- TypeScript 生态在文档和数据处理领域长期存在碎片化问题。处理 Excel、PDF、CSV、XML、ZIP 和流式操作,往往需要分别引入不同的包;到了浏览器端又要换一套方案;流式处理还得再额外接入一个适配库。这些库的 API 风格、质量和维护状态参差不齐,给每个需要组合使用它们的项目都带来了额外的负担。
5
+ TypeScript 生态在文档和数据处理领域长期存在碎片化问题。要处理电子表格、文档、PDF 以及围绕它们的各种数据和归档格式,往往需要分别引入不同的包;到了浏览器端又要换一套方案;流式处理还得再额外接入一个适配库。这些库的 API 风格、质量和维护状态参差不齐,给每个需要组合使用它们的项目都带来了额外的负担。
6
6
 
7
7
  ExcelTS 正是为了解决这个问题而生。一个包、一套 API、一份代码 — 在 Node.js、Bun 和浏览器中行为完全一致。流式处理是每个模块的一等公民,而非通过第三方适配器后期拼装的附属品。目标很简单:安装一次,按需导入,在任何环境下都获得相同的可靠体验 — 同时将流式处理的性能发挥到极致。
8
8
 
@@ -12,13 +12,13 @@ ExcelTS 是一个零依赖的 TypeScript 电子表格和文档工具包:
12
12
 
13
13
  - **AI 友好** — 简洁一致的 API,专为 AI 编程助手设计。每个模块都配有完整的文档和可运行的示例供 AI 学习
14
14
  - **零运行时依赖** — 纯 TypeScript,无外部包
15
- - **八大模块** — Excel、Formula、PDF、CSV、Markdown、XML、Archive、Stream
15
+ - **九大模块** — Excel、Word、Formula、PDF、CSV、Markdown、XML、Archive、Stream
16
16
  - **跨平台** — Node.js 22+、Bun、Chrome 89+、Firefox 102+、Safari 14.1+
17
17
  - **ESM 优先** — 原生 ES Modules,兼容 CommonJS,完整的 tree-shaking 支持
18
18
 
19
19
  ## 模块
20
20
 
21
- ExcelTS 由八个独立模块组成,每个模块都有自己的文档和可运行示例。
21
+ ExcelTS 由九个独立模块组成,每个模块都有自己的文档和可运行示例。
22
22
 
23
23
  ### Excel — XLSX/JSON 工作簿管理器
24
24
 
@@ -27,6 +27,13 @@ ExcelTS 由八个独立模块组成,每个模块都有自己的文档和可运
27
27
  - [文档](src/modules/excel/README.md) | [中文](src/modules/excel/README_zh.md)
28
28
  - [示例](src/modules/excel/examples/)
29
29
 
30
+ ### Word — DOCX 文档处理器
31
+
32
+ 读取、写入和操作 DOCX 文件,提供完整的构建器、读取器和转换器能力。可构建含标题、表格、图片、列表、页眉/页脚、绘图形状、数学公式和图表的文档;可对现有文件做文本查找/替换、格式感知查询、书签/批注查找等读取与修改;可与 HTML 和 Markdown 互相转换,将 Excel 工作簿桥接为 Word 表格,并将 Word 直接渲染为 PDF。高级功能包括模板引擎、表单字段、OpenDoPE 数据绑定、字体嵌入与子集化、修订追踪接受/拒绝、文档比对/合并、流式写入器、密码保护、Agile 加密解密以及数字签名检测。
33
+
34
+ - [文档](src/modules/word/README.md) | [中文](src/modules/word/README_zh.md)
35
+ - [示例](src/modules/word/examples/)
36
+
30
37
  ### Formula — Excel 兼容公式引擎
31
38
 
32
39
  独立的 433 函数计算引擎,包含 tokenizer、parser、依赖图、动态数组 spill,支持 `LAMBDA`/`LET`/`MAP`/`REDUCE`。作为单独的 subpath 发布,不会被打进只读写 XLSX 的 bundle。**两种使用模式**:通过 `installFormulaEngine()` 和 `Workbook` 配合使用,或通过 `calculateFormulas()` 对任意 `WorkbookLike` 宿主单独使用 — 引擎本身**零 excel 运行时依赖**。
@@ -135,6 +142,14 @@ const archive = await zip().add("hello.txt", "Hello!").bytes();
135
142
  import { parseMarkdown, formatMarkdown } from "@cj-tech-master/excelts/markdown";
136
143
  const table = parseMarkdown("| A | B |\n|---|---|\n| 1 | 2 |");
137
144
 
145
+ // Word — 创建、读取和转换 DOCX
146
+ import { Document, toBuffer, readDocx } from "@cj-tech-master/excelts/word";
147
+ const wdoc = Document.create();
148
+ Document.addHeading(wdoc, "报告", 1);
149
+ Document.addParagraph(wdoc, "由 ExcelTS 生成。");
150
+ const docxBytes = await toBuffer(Document.build(wdoc));
151
+ const parsedDocx = await readDocx(docxBytes); // 往返读取
152
+
138
153
  // Formula — 可选的公式引擎(默认不打进主 bundle)
139
154
  //
140
155
  // 模式 A: 配合 Workbook — 启用 wb.calculateFormulas()
@@ -43,6 +43,10 @@ export interface NoteConfig {
43
43
  };
44
44
  editAs?: string;
45
45
  anchor?: string;
46
+ /** Comment box width in points. Defaults to 97.8pt when omitted. */
47
+ width?: number;
48
+ /** Comment box height in points. Defaults to 59.1pt when omitted. */
49
+ height?: number;
46
50
  }
47
51
  export interface NoteModel {
48
52
  type: string;
@@ -36,7 +36,11 @@ class Note {
36
36
  set model(value) {
37
37
  const { note } = value;
38
38
  const { texts } = note;
39
- if (texts && texts.length === 1 && Object.keys(texts[0]).length === 1) {
39
+ // A single, plain text run with no extra box geometry can be flattened
40
+ // back to a simple string. Custom width/height must keep the full config
41
+ // so the sizing survives the model round-trip.
42
+ const hasCustomSize = note.width !== undefined || note.height !== undefined;
43
+ if (texts && texts.length === 1 && Object.keys(texts[0]).length === 1 && !hasCustomSize) {
40
44
  this.note = texts[0].text;
41
45
  }
42
46
  else {
@@ -213,12 +213,13 @@ class Row {
213
213
  // (e.g. DB entities). Cell.value setter handles unknown values via Value.getType
214
214
  // fallback to JSON type, so this cast is safe at runtime.
215
215
  this._worksheet.eachColumnKey((column, key) => {
216
- if (value[key] !== undefined) {
216
+ const resolved = resolveColumnKeyValue(value, key);
217
+ if (resolved !== undefined) {
217
218
  this.getCellEx({
218
219
  address: colCache.encodeAddress(this._number, column.number),
219
220
  row: this._number,
220
221
  col: column.number
221
- }).value = value[key];
222
+ }).value = resolved;
222
223
  }
223
224
  });
224
225
  }
@@ -459,4 +460,36 @@ class Row {
459
460
  this.style = value.style ? structuredClone(value.style) : {};
460
461
  }
461
462
  }
463
+ /**
464
+ * Resolve a column key against a row object, supporting dotted nested paths.
465
+ *
466
+ * A key without a `.` takes the original fast path (`obj[key]`), preserving
467
+ * exact backward compatibility — including keys that legitimately contain a
468
+ * dot only as a flat property name, which still resolve via the fast path
469
+ * first. A dotted key (e.g. `"address.city"`) is resolved by walking each
470
+ * segment; if any segment is missing or not an object, the result is
471
+ * `undefined` (the same signal the caller already uses to skip a cell).
472
+ *
473
+ * @param obj - The row object supplied to `row.values = {...}` / `addRow({...})`.
474
+ * @param key - The column key, optionally a dotted path.
475
+ * @returns The resolved value, or `undefined` when the path cannot be followed.
476
+ */
477
+ function resolveColumnKeyValue(obj, key) {
478
+ // Fast path: a flat key (no dot) or an exact flat property match. This keeps
479
+ // existing behaviour identical and also lets a literal "a.b" property win
480
+ // over nested traversal when it is actually present on the object.
481
+ const direct = obj[key];
482
+ if (direct !== undefined || !key.includes(".")) {
483
+ return direct;
484
+ }
485
+ // Dotted path: walk segments, bailing out to undefined on any gap.
486
+ let current = obj;
487
+ for (const segment of key.split(".")) {
488
+ if (current === null || typeof current !== "object") {
489
+ return undefined;
490
+ }
491
+ current = current[segment];
492
+ }
493
+ return current;
494
+ }
462
495
  export { Row };
@@ -20,9 +20,16 @@ import type { Writable } from "../../stream/index.js";
20
20
  * Extends the public {@link ImageData} shape with the unique stored name
21
21
  * (`name`) assigned by `addImage`, and pins `type` to `"image"`.
22
22
  */
23
- export interface Medium extends ImageData {
23
+ export interface Medium extends Omit<ImageData, "extension"> {
24
24
  type: "image";
25
25
  name: string;
26
+ /**
27
+ * Widened from `ImageData.extension` so an SVG companion medium can carry
28
+ * the `"svg"` extension (the public `addImage` input stays raster-only).
29
+ */
30
+ extension: string;
31
+ /** Media index of an SVG companion (raster blip + svgBlip extension). */
32
+ svgMediaId?: number;
26
33
  }
27
34
  interface CommentRef {
28
35
  commentName: string;
@@ -387,13 +387,33 @@ export class WorkbookWriterBase {
387
387
  * ```
388
388
  */
389
389
  addImage(image) {
390
+ const { svg, ...raster } = image;
391
+ if (svg &&
392
+ raster.link &&
393
+ raster.buffer == null &&
394
+ raster.base64 == null &&
395
+ raster.filename == null) {
396
+ throw new ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
397
+ }
390
398
  const id = this.media.length;
391
399
  const medium = {
392
- ...image,
400
+ ...raster,
393
401
  type: "image",
394
- name: `image${id}.${image.extension}`
402
+ name: `image${id}.${raster.extension}`
395
403
  };
396
404
  this.media.push(medium);
405
+ if (svg) {
406
+ // Register the SVG companion as a second image medium and link it back to
407
+ // the raster blip so the drawing serializer emits the svgBlip extension.
408
+ const svgId = this.media.length;
409
+ this.media.push({
410
+ ...svg,
411
+ type: "image",
412
+ extension: "svg",
413
+ name: `image${svgId}.svg`
414
+ });
415
+ medium.svgMediaId = svgId;
416
+ }
397
417
  return id;
398
418
  }
399
419
  getImage(id) {
@@ -497,6 +497,24 @@ export interface ImageData {
497
497
  * are required.
498
498
  */
499
499
  link?: string;
500
+ /**
501
+ * Attach a scalable SVG alongside a raster fallback.
502
+ *
503
+ * Excel stores SVG pictures as a raster `a:blip` (the `extension`/`buffer`/
504
+ * `base64`/`filename` on this object — typically a PNG) plus an
505
+ * `asvg:svgBlip` extension pointing at the vector data. The raster image is
506
+ * what older Excel versions and non-SVG consumers render, so it is required;
507
+ * modern Excel renders the crisp SVG. This library does **not** rasterize —
508
+ * you supply both the SVG bytes and the raster fallback you want embedded.
509
+ */
510
+ svg?: {
511
+ /** SVG bytes (mutually use one of buffer/base64/filename). */
512
+ buffer?: Buffer;
513
+ /** Base64-encoded SVG. */
514
+ base64?: string;
515
+ /** Path to an `.svg` file (Node only). */
516
+ filename?: string;
517
+ };
500
518
  }
501
519
  export interface ImagePosition {
502
520
  tl: {
@@ -556,6 +574,69 @@ export interface ImageHyperlinkValue {
556
574
  hyperlink: string;
557
575
  tooltip?: string;
558
576
  }
577
+ /**
578
+ * Preset geometry for a drawing shape. Mirrors the OOXML `prst` vocabulary;
579
+ * the most common presets are surfaced here, but any valid preset name is
580
+ * accepted as a fallback `string`.
581
+ */
582
+ export type ShapeType = "rect" | "roundRect" | "ellipse" | "triangle" | "line" | "rightArrow" | "leftArrow" | "upArrow" | "downArrow" | "diamond" | "hexagon" | "star5" | (string & {});
583
+ /** Options for `Worksheet.addShape`. */
584
+ export interface AddShapeOptions {
585
+ /** Preset geometry (defaults to `"rect"`). */
586
+ type?: ShapeType;
587
+ /** Where the shape sits — a cell range (e.g. `"B2:D5"`) or anchor object. */
588
+ range: AddImageRange;
589
+ /** Solid fill colour as hex RGB (e.g. `"FF0000"`). Omit for no fill. */
590
+ fillColor?: string;
591
+ /** Outline colour as hex RGB (e.g. `"000000"`). */
592
+ lineColor?: string;
593
+ /** Outline width in points. */
594
+ lineWidth?: number;
595
+ /** Optional centred text label. */
596
+ text?: string;
597
+ /** Display name (defaults to `"Shape N"`). */
598
+ name?: string;
599
+ }
600
+ /** Internal serialized model for a worksheet shape. */
601
+ export interface ShapeModel {
602
+ type: "shape";
603
+ shapeType: string;
604
+ range: AddImageRange;
605
+ fillColor?: string;
606
+ lineColor?: string;
607
+ lineWidth?: number;
608
+ text?: string;
609
+ name?: string;
610
+ /**
611
+ * Resolved anchor coordinates, filled in by the worksheet model getter so
612
+ * the serializer doesn't need range-parsing logic. Mirrors the three image
613
+ * anchoring modes: two-cell (`tl`+`br`), one-cell (`tl`+`ext`) and absolute
614
+ * (`pos`+`ext`). Internal only.
615
+ */
616
+ anchorRange?: {
617
+ tl: {
618
+ nativeCol: number;
619
+ nativeColOff: number;
620
+ nativeRow: number;
621
+ nativeRowOff: number;
622
+ };
623
+ br?: {
624
+ nativeCol: number;
625
+ nativeColOff: number;
626
+ nativeRow: number;
627
+ nativeRowOff: number;
628
+ };
629
+ ext?: {
630
+ width?: number;
631
+ height?: number;
632
+ };
633
+ pos?: {
634
+ x: number;
635
+ y: number;
636
+ };
637
+ editAs?: string;
638
+ };
639
+ }
559
640
  /**
560
641
  * Watermark placement mode in the Excel worksheet.
561
642
  *
@@ -19,6 +19,12 @@ export interface DrawingAnchor {
19
19
  * (`<a:blip r:link>`) instead of an embedded one (`<a:blip r:embed>`).
20
20
  */
21
21
  external?: boolean;
22
+ /**
23
+ * Relationship id of an SVG companion. When set, the raster `a:blip`
24
+ * (referenced by `rId`) carries an `asvg:svgBlip` extension pointing at
25
+ * the SVG media via this id.
26
+ */
27
+ svgRId?: string;
22
28
  };
23
29
  range: any;
24
30
  }
@@ -54,6 +60,8 @@ export interface MediaLike {
54
60
  buffer?: unknown;
55
61
  base64?: unknown;
56
62
  filename?: unknown;
63
+ /** Media index of an SVG companion (raster blip + svgBlip extension). */
64
+ svgMediaId?: number;
57
65
  }
58
66
  /**
59
67
  * Resolves a media filename into the drawing-level relative target path.
@@ -122,6 +122,23 @@ export function buildDrawingAnchorsAndRels(media, existingRels, options) {
122
122
  },
123
123
  range: medium.range
124
124
  };
125
+ // SVG companion: allocate (and dedupe) a rel for the vector media, then
126
+ // record its rId so the blip serializer emits the asvg:svgBlip extension.
127
+ if (bookImage.svgMediaId !== undefined) {
128
+ const svgKey = `svg:${bookImage.svgMediaId}`;
129
+ let rIdSvg = imageRIdMap[svgKey];
130
+ if (!rIdSvg) {
131
+ const svgImage = options.getBookImage(bookImage.svgMediaId);
132
+ if (svgImage) {
133
+ rIdSvg = options.nextRId(rels);
134
+ imageRIdMap[svgKey] = rIdSvg;
135
+ rels.push(buildImageRel(rIdSvg, svgImage));
136
+ }
137
+ }
138
+ if (rIdSvg) {
139
+ anchor.picture.svgRId = rIdSvg;
140
+ }
141
+ }
125
142
  // Pass through watermark opacity as alphaModFix
126
143
  if (medium.opacity !== undefined) {
127
144
  const clamped = Math.max(0, Math.min(1, medium.opacity));
@@ -172,8 +189,8 @@ export function filterDrawingAnchors(anchors) {
172
189
  if (a.range?.br && a.shape) {
173
190
  return true;
174
191
  }
175
- // One-cell anchors need a valid picture or graphicFrame (charts)
176
- if (!a.range?.br && !a.picture && !a.graphicFrame) {
192
+ // One-cell anchors need a valid picture, graphicFrame (charts) or shape.
193
+ if (!a.range?.br && !a.picture && !a.graphicFrame && !a.shape) {
177
194
  return false;
178
195
  }
179
196
  // Two-cell anchors need either picture, shape, or graphicFrame (charts)
@@ -34,6 +34,12 @@ export interface WorkbookMedia {
34
34
  name?: string;
35
35
  /** External link target — when set, the image is referenced, not embedded. */
36
36
  link?: string;
37
+ /**
38
+ * Media index of the SVG companion for this raster image. When set, the
39
+ * picture is written as a raster `a:blip` plus an `asvg:svgBlip` extension
40
+ * referencing the SVG media at this index. Internal bookkeeping only.
41
+ */
42
+ svgMediaId?: number;
37
43
  }
38
44
  /** Internal model type for serialization */
39
45
  export interface WorkbookModel {
@@ -820,6 +826,16 @@ declare class Workbook {
820
826
  * const id = workbook.addImage({ extension: "png", link: "https://example.com/logo.png" });
821
827
  * worksheet.addImage(id, "B2:D6");
822
828
  * ```
829
+ *
830
+ * @example SVG with raster fallback — crisp in modern Excel, safe everywhere
831
+ * ```typescript
832
+ * const id = workbook.addImage({
833
+ * buffer: pngFallbackBytes, // shown by older Excel / non-SVG consumers
834
+ * extension: "png",
835
+ * svg: { buffer: svgBytes } // shown by Excel 2016+
836
+ * });
837
+ * worksheet.addImage(id, "B2:D6");
838
+ * ```
823
839
  */
824
840
  addImage(image: ImageData): number;
825
841
  getImage(id: number | string): WorkbookMedia | undefined;
@@ -20,7 +20,7 @@ import { parseNumberFromCsv } from "../csv/utils/number.js";
20
20
  import { getChartSupport } from "./chart-host-registry.js";
21
21
  import { Chartsheet } from "./chartsheet.js";
22
22
  import { DefinedNames } from "./defined-names.js";
23
- import { ExcelDownloadError, ExcelNotSupportedError, WorksheetNameError } from "./errors.js";
23
+ import { ExcelDownloadError, ExcelNotSupportedError, ImageError, WorksheetNameError } from "./errors.js";
24
24
  import { withPivotChartSource } from "./pivot-chart.js";
25
25
  import { WorkbookReader } from "./stream/workbook-reader.browser.js";
26
26
  import { WorkbookWriter } from "./stream/workbook-writer.browser.js";
@@ -1733,10 +1733,40 @@ class Workbook {
1733
1733
  * const id = workbook.addImage({ extension: "png", link: "https://example.com/logo.png" });
1734
1734
  * worksheet.addImage(id, "B2:D6");
1735
1735
  * ```
1736
+ *
1737
+ * @example SVG with raster fallback — crisp in modern Excel, safe everywhere
1738
+ * ```typescript
1739
+ * const id = workbook.addImage({
1740
+ * buffer: pngFallbackBytes, // shown by older Excel / non-SVG consumers
1741
+ * extension: "png",
1742
+ * svg: { buffer: svgBytes } // shown by Excel 2016+
1743
+ * });
1744
+ * worksheet.addImage(id, "B2:D6");
1745
+ * ```
1736
1746
  */
1737
1747
  addImage(image) {
1748
+ const { svg, ...raster } = image;
1749
+ if (svg &&
1750
+ raster.link &&
1751
+ raster.buffer == null &&
1752
+ raster.base64 == null &&
1753
+ raster.filename == null) {
1754
+ // An SVG companion needs an embedded raster fallback; a *linked* (external)
1755
+ // raster has no package part to attach the svgBlip extension to.
1756
+ throw new ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
1757
+ }
1738
1758
  const id = this.media.length;
1739
- this.media.push({ ...image, type: "image" });
1759
+ const rasterMedia = { ...raster, type: "image" };
1760
+ this.media.push(rasterMedia);
1761
+ if (svg) {
1762
+ // Register the SVG as a second `type: "image"` media so it flows through
1763
+ // the existing media naming, content-types, and zip-writing paths. Link
1764
+ // it back to the raster blip so the drawing serializer can emit the
1765
+ // asvg:svgBlip extension.
1766
+ const svgId = this.media.length;
1767
+ this.media.push({ ...svg, type: "image", extension: "svg" });
1768
+ rasterMedia.svgMediaId = svgId;
1769
+ }
1740
1770
  return id;
1741
1771
  }
1742
1772
  getImage(id) {
@@ -14,7 +14,7 @@ import { Range, type RangeInput } from "./range.js";
14
14
  import { Row, type RowModel } from "./row.js";
15
15
  import type { AddSparklineGroupOptions, SparklineGroup } from "./sparkline/index.js";
16
16
  import { Table, type TableModel } from "./table.js";
17
- import type { AddImageRange, AutoFilter, CellValue, ColBreak, ConditionalFormattingOptions, DataValidation, IgnoredError, RowBreak, RowValues, TableProperties, ThreadedComment, WatermarkOptions, WorksheetProperties, WorksheetState, WorksheetView } from "./types.js";
17
+ import type { AddImageRange, AddShapeOptions, AutoFilter, CellValue, ColBreak, ConditionalFormattingOptions, DataValidation, IgnoredError, RowBreak, RowValues, ShapeModel, TableProperties, ThreadedComment, WatermarkOptions, WorksheetProperties, WorksheetState, WorksheetView } from "./types.js";
18
18
  import { type Origin } from "./utils/address.js";
19
19
  import type { Workbook } from "./workbook.js";
20
20
  type DataValidationModel = {
@@ -110,6 +110,7 @@ interface WorksheetModel {
110
110
  views: Partial<WorksheetView>[];
111
111
  autoFilter: AutoFilter | null;
112
112
  media: ImageModel[];
113
+ shapes?: ShapeModel[];
113
114
  sheetProtection: SheetProtection | null;
114
115
  tables: TableModel[];
115
116
  pivotTables: PivotTable[];
@@ -156,6 +157,7 @@ declare class Worksheet {
156
157
  views: Partial<WorksheetView>[];
157
158
  autoFilter: AutoFilter | null;
158
159
  private _media;
160
+ private _shapes;
159
161
  private _charts;
160
162
  private _sparklineGroups;
161
163
  sheetProtection: SheetProtection | null;
@@ -362,6 +364,34 @@ declare class Worksheet {
362
364
  * embed an image within the worksheet to cover a range
363
365
  */
364
366
  addImage(imageId: string | number, range: AddImageRange): void;
367
+ /**
368
+ * Add a free-form drawing shape (rectangle, ellipse, line, text box, …) to
369
+ * the worksheet, anchored to a cell range.
370
+ *
371
+ * Unlike images, shapes need no media file — the geometry, fill, outline and
372
+ * optional text label are written directly into the drawing part.
373
+ *
374
+ * @example
375
+ * ```typescript
376
+ * worksheet.addShape({
377
+ * type: "rect",
378
+ * range: "B2:D5",
379
+ * fillColor: "FFD966",
380
+ * lineColor: "000000",
381
+ * lineWidth: 1,
382
+ * text: "Important"
383
+ * });
384
+ * ```
385
+ */
386
+ addShape(options: AddShapeOptions): void;
387
+ /** All shapes added to this worksheet. */
388
+ getShapes(): ShapeModel[];
389
+ /**
390
+ * Resolve a shape's `range` into concrete two-cell anchor coordinates,
391
+ * reusing the `Image` range parser so cell-address/anchor handling stays in
392
+ * one place. Returns a serializable ShapeModel for the worksheet xform.
393
+ */
394
+ private _resolveShapeModel;
365
395
  getImages(): Image[];
366
396
  /**
367
397
  * Add a chart to the worksheet, positioned at the given range.
@@ -105,6 +105,8 @@ class Worksheet {
105
105
  this.autoFilter = options.autoFilter ?? null;
106
106
  // for images, etc
107
107
  this._media = [];
108
+ // for user-drawn shapes (rectangles, lines, text boxes, …)
109
+ this._shapes = [];
108
110
  // for charts
109
111
  this._charts = [];
110
112
  this._sparklineGroups = [];
@@ -1009,6 +1011,85 @@ class Worksheet {
1009
1011
  };
1010
1012
  this._media.push(new Image(this, model));
1011
1013
  }
1014
+ /**
1015
+ * Add a free-form drawing shape (rectangle, ellipse, line, text box, …) to
1016
+ * the worksheet, anchored to a cell range.
1017
+ *
1018
+ * Unlike images, shapes need no media file — the geometry, fill, outline and
1019
+ * optional text label are written directly into the drawing part.
1020
+ *
1021
+ * @example
1022
+ * ```typescript
1023
+ * worksheet.addShape({
1024
+ * type: "rect",
1025
+ * range: "B2:D5",
1026
+ * fillColor: "FFD966",
1027
+ * lineColor: "000000",
1028
+ * lineWidth: 1,
1029
+ * text: "Important"
1030
+ * });
1031
+ * ```
1032
+ */
1033
+ addShape(options) {
1034
+ const range = options.range;
1035
+ // A shape must cover an area, mirroring images. Reject inputs that resolve
1036
+ // to no size up front, with a clear shape-specific message — otherwise the
1037
+ // failure surfaces much later as a confusing `ImageError` from the internal
1038
+ // range parser when the worksheet is serialized.
1039
+ const hasArea = (typeof range === "string" && range.includes(":")) ||
1040
+ (typeof range === "object" &&
1041
+ range !== null &&
1042
+ ("br" in range || "ext" in range || "pos" in range));
1043
+ if (!hasArea) {
1044
+ throw new ImageError('addShape requires a range covering an area: a cell range like "B2:D5", or an object with `br`, `ext`, or `pos`.');
1045
+ }
1046
+ this._shapes.push({
1047
+ type: "shape",
1048
+ shapeType: options.type ?? "rect",
1049
+ range,
1050
+ fillColor: options.fillColor,
1051
+ lineColor: options.lineColor,
1052
+ lineWidth: options.lineWidth,
1053
+ text: options.text,
1054
+ name: options.name
1055
+ });
1056
+ }
1057
+ /** All shapes added to this worksheet. */
1058
+ getShapes() {
1059
+ return this._shapes.slice();
1060
+ }
1061
+ /**
1062
+ * Resolve a shape's `range` into concrete two-cell anchor coordinates,
1063
+ * reusing the `Image` range parser so cell-address/anchor handling stays in
1064
+ * one place. Returns a serializable ShapeModel for the worksheet xform.
1065
+ */
1066
+ _resolveShapeModel(shape) {
1067
+ let range;
1068
+ try {
1069
+ const probe = new Image(this, { type: "image", imageId: "", range: shape.range });
1070
+ // The probe is always an "image" type, so its model carries `range`.
1071
+ range = probe.model.range;
1072
+ }
1073
+ catch {
1074
+ // Range could not be parsed into an anchor (addShape validates the common
1075
+ // cases up front; this guards exotic inputs). Drop the anchor so the
1076
+ // serializer skips this shape rather than failing the whole worksheet.
1077
+ range = undefined;
1078
+ }
1079
+ if (!range) {
1080
+ return { ...shape, anchorRange: undefined };
1081
+ }
1082
+ return {
1083
+ ...shape,
1084
+ anchorRange: {
1085
+ tl: range.tl,
1086
+ br: range.br,
1087
+ ext: range.ext,
1088
+ pos: range.pos,
1089
+ editAs: range.editAs
1090
+ }
1091
+ };
1092
+ }
1012
1093
  getImages() {
1013
1094
  return this._media.filter(m => m.type === "image");
1014
1095
  }
@@ -1754,6 +1835,7 @@ class Worksheet {
1754
1835
  views: this.views,
1755
1836
  autoFilter: this.autoFilter,
1756
1837
  media: this._media.map(medium => medium.model),
1838
+ shapes: this._shapes.map(shape => this._resolveShapeModel(shape)),
1757
1839
  sheetProtection: this.sheetProtection,
1758
1840
  tables: Object.values(this.tables).map(table => table.model),
1759
1841
  pivotTables: this.pivotTables,
@@ -1819,6 +1901,7 @@ class Worksheet {
1819
1901
  this.views = value.views;
1820
1902
  this.autoFilter = value.autoFilter;
1821
1903
  this._media = value.media.map(medium => new Image(this, medium));
1904
+ this._shapes = value.shapes ? value.shapes.slice() : [];
1822
1905
  // Restore watermark state from media entries
1823
1906
  this._watermark = value.watermark ?? null;
1824
1907
  if (!this._watermark) {